package edu.iastate.cs228.hw1;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Scanner;

import edu.iastate.cs228.hw1.Animal.Gender;

/**
 * This class models a River in which will be used to simulate an ecosystem with
 * bears and fish
 * 
 * @author Devon Eilers
 * 
 */
public class River 
{
	/**
	 * The river in which the animals reside in.
	 */
	public Animal[] river;

	/**
	 * The length of the river.
	 */
	public int length;

	/**
	 * The seed value for the random number generator used in updating the
	 * river.
	 */
	public long seed;

	/**
	 * Constructs a river from a file.
	 * 
	 * @param inputFileName
	 *            The name of the file to be read from.
	 * @throws FileNotFoundException
	 */
	public River(String inputFileName) throws FileNotFoundException 
	{
		Scanner scan = new Scanner(new File(inputFileName));
		ArrayList<Animal> animals = new ArrayList<Animal>();
		
 		while (scan.hasNext())
		{
			String s = scan.next();
			
			String type = String.valueOf(s.charAt(0));
			String gender = String.valueOf(s.charAt(1));
			Gender gen;
			if (gender.equals("M"))
				gen = Gender.MALE;
			else
				gen = Gender.FEMALE;
			
			String age = String.valueOf(s.charAt(2));
			
			if(type.equals("B"))
			{
				animals.add(new Bear(Integer.parseInt(age), gen));
			}
			else if (type.equals("F"))
			{
				animals.add(new Fish(Integer.parseInt(age), gen));
			}
			else // empty river slot
			{
				animals.add(null);
			}
		}		
 		
 		river = new Animal[animals.size()];
 		// converting array list to an array of type Animal
 		for (int i = 0; i < animals.size(); ++i)
 		{
 			river[i] = animals.get(i);
 		}
 		


 		length = river.length;
 		scan.close();
	}

	/**
	 * Generates a random river ecosystem of the given length.
	 * 
	 * @param length
	 *            The length of the river.
	 */
	public River(int length) 
	{
		this.length = length;
		river = new Animal[length];
		
		for (int i = 0; i < length; ++i)
		{
			int speciesCase = RandomSingleton.getInstance().nextInt(3);
			Gender gen = getRandomGender();
			
			if (speciesCase==0)
			{
				river[i] = null;
			}
			else if (speciesCase==1)
			{				
				int ageCase = RandomSingleton.getInstance().nextInt(Animal.BEAR_MAX_AGE+1);
				
				river[i] = new Bear(ageCase, gen);
			}
			else
			{
				int ageCase = RandomSingleton.getInstance().nextInt(Animal.FISH_MAX_AGE+1);
				
				river[i] = new Fish(ageCase, gen);
			}
			
		}
	}

	/**
	 * Generates a random river ecosystem of the given length, where the seed of
	 * the random number is as given.
	 * 
	 * @param length
	 *            The length of the river.
	 * @param seed
	 *            The seed value used for the random number generator.
	 */
	public River(int length, long seed) 
	{
		this.length = length;
		this.seed = seed;
		RandomSingleton.getInstance().setSeed(seed);
		river = new Animal[length];
		
		for (int i = 0; i < length; ++i)
		{
			int speciesCase = RandomSingleton.getInstance().nextInt(3);
			Gender gen = getRandomGender();
			
			if (speciesCase==0)
			{
				river[i] = null;
			}
			else if (speciesCase==1)
			{				
				int ageCase = RandomSingleton.getInstance().nextInt(Animal.BEAR_MAX_AGE+1);
				
				river[i] = new Bear(ageCase, gen);
			}
			else
			{
				int ageCase = RandomSingleton.getInstance().nextInt(Animal.FISH_MAX_AGE+1);
				
				river[i] = new Fish(ageCase, gen);
			}
			
		}
	}

	/**
	 * Returns the length of the river.
	 * 
	 * @return The length of the river.
	 */
	public int getLength() 
	{
		return length;
	}

	/**
	 * Sets the seed of the random number generator used in updating the river.
	 * 
	 * @param seed
	 *            The seed value used for the random number generator.
	 */
	public void setSeed(long seed) 
	{
		RandomSingleton.getInstance().setSeed(seed);
		this.seed = seed;
	}

	/**
	 * Returns the number of empty (null) cells in the river.
	 * 
	 * @return The number of empty (null) cells.
	 */
	public int numEmpty() 
	{
		int count = 0;
		
		for (Animal a : river)
		{
			if (a==null) count++;
		}
		
		return count;
	}

	/**
	 * If the river has no empty (null) cells, then do nothing and return false.
	 * Otherwise, add an animal of age 0 of randomly chosen gender and of the
	 * same type as animal to a cell chosen uniformly at random from among the
	 * currently empty cells and return true.
	 * 
	 * @param animal
	 *            The type of animal that should be added to the river.
	 * @return True if an animal was added, false otherwise.
	 */
	public boolean addRandom(Animal animal) 
	{
		int emptySlots = numEmpty();
		
		if(emptySlots==0)
			return false;
		else
		{
			int randomEmptySlot = RandomSingleton.getInstance().nextInt(emptySlots);
			Gender gen = getRandomGender();
			Animal toAdd;
			
			if (animal.getAnimalType().equals("Bear"))
			{
				toAdd = new Bear(0, gen);
			}
			else
			{
				toAdd = new Fish(0, gen);
			}

			int count = 0;
			for (int i = 0; i < river.length; ++i)
			{
				if (river[i] == null && randomEmptySlot==count)
				{
					river[i] = toAdd;
					break;
				}
				else if (river[i] == null)
				{
					count++;
				}
					
			}
			return true;
		}
	}

	/**
	 * Process the object at cell i, following the rules given in the Project
	 * Description. If it is null, do nothing. If it is an animal and it has
	 * reached the end of its lifespan, the animal dies. Otherwise, decides
	 * which direction, if any, the animal should move, and what other actions
	 * to take (including the creation of a child), following the rules given in
	 * the Project Description.
	 * 
	 * @param i
	 *            The index of the cell to be updated.
	 */
	public void updateCell(int i) 
	{
		Animal cur = river[i];
		if (cur==null) return; // slot is empty
		else
		{
			int moveDir = RandomSingleton.getInstance().nextInt(3);
			if(moveDir==0) // moving left
			{
				if (i==0) // at the very left of the river
				{
					Animal leftSlot = river[river.length-1];
					if (leftSlot==null) // empty destination
					{
						river[river.length-1] = cur;
						river[i] = null;
						return;
					}
					else if (bothBears(leftSlot, cur)) // both bears
					{
						Bear curBear = (Bear) cur;
						Bear leftSlotBear = (Bear) leftSlot;
						
						if (sameGender(leftSlotBear, curBear)) // same gender
						{
							if (curBear.getStrength()==leftSlotBear.getStrength()) // same strength
							{
								// nothing happens

								return;
							}
							else if (curBear.getStrength() > leftSlotBear.getStrength()) // moving bear is stronger
							{
								river[river.length-1] = curBear;
								river[i] = null;
								return;
							}
							else if (curBear.getStrength() < leftSlotBear.getStrength()) // moving bear is weaker
							{
								river[i] = null;
								return;
							}
							
						}
						else // opposite gender
						{
							addRandom(curBear);
							return;
						}
					}
					else if (bothFish(leftSlot, cur)) // both fish
					{
						// nothing happens
						return;
					}
					else if (!cur.getAnimalType().equals(leftSlot.getAnimalType())) // bear and fish
					{
						if (cur.getAnimalType().equals("Bear"))
						{
							river[river.length-1] = cur;
							river[i] = null;
							return;
						}
						else // fish is moving to a bear's slot
						{
							river[i] = null;
							return;
						}
					}
				}
				else // normal left moving case
				{
					Animal leftSlot = river[i-1];
					if (leftSlot == null) // slot is empty
					{
						river[i-1] = cur;
						river[i] = null;
						return;
					}
					else if (bothBears(leftSlot, cur)) // both bears
					{
						Bear leftSlotBear = (Bear) leftSlot;
						Bear curBear = (Bear) cur;
						
						if (sameGender(leftSlotBear, curBear)) // same gender
						{
							if (curBear.getStrength()==leftSlotBear.getStrength()) // same strength
							{
								// nothing happens

								return;
							}
							else if (curBear.getStrength() > leftSlotBear.getStrength()) // moving bear is stronger
							{
								river[i-1] = curBear;
								river[i] = null;
								return;
							}
							else if (curBear.getStrength() < leftSlotBear.getStrength()) // moving bear is weaker
							{
								river[i] = null;
								return;
							}
							
						}
						else // opposite gender
						{
							addRandom(curBear);
						}
					}
					else if (bothFish(leftSlot, cur)) // both fish
					{
						if (sameGender(leftSlot, cur)) // same gender
						{
							// nothing happens
							return;
						}
						else // opposite gender (make a baby
						{
							addRandom(cur);
						}
					}
					else if (!cur.getAnimalType().equals(leftSlot.getAnimalType())) // bear and fish
					{
						if (cur.getAnimalType().equals("Bear"))
						{
							river[river.length-1] = cur;
							river[i] = null;
							return;
						}
						else // fish is moving to a bear's slot
						{
							river[i] = null;
							return;
						}						
					}
				}
			}
			else if (moveDir==1) // does not move at all
			{
				// nothing happens
				return;
			}
			else // moving right
			{
				if (i==river.length-1) // looking at the rightmost cell
				{
					Animal rightSlot = river[0];
					
					if (rightSlot==null) // empty destination
					{
						river[0] = cur;
						river[i] = null;
						return;
					}
					else if (bothBears(rightSlot, cur)) // both bears
					{
						Bear curBear = (Bear) cur;
						Bear rightSlotBear = (Bear) rightSlot;
						
						if (curBear.gender.equals(rightSlotBear.gender)) // genders are same
						{
							if (curBear.getStrength() > rightSlotBear.getStrength()) // moving bear is stronger
							{
								river[0] = curBear;
								river[i] = null;
								return;
							}
							else if (curBear.getStrength() < rightSlotBear.getStrength()) // moving bear is weaker
							{
								river[i] = null;
								return;
							}
							else // equal strength
							{
								// nothing happens
								return;
							}
						}
						else // different gender
						{
							addRandom(curBear);	
							return;
						}
					}
					else if (bothFish(rightSlot, cur)) // both fish
					{
						if (sameGender(rightSlot, cur)) // same gender
						{
							// do nothing
							return;
						}
						else // opposite (make a baby)
						{
							addRandom(cur);
							return;
						}
					}
					else if (!cur.getAnimalType().equals(rightSlot.getAnimalType())) // bear and fish
					{
						if (cur.getAnimalType().equals("Bear")) // bear moving to fish slot
						{
							river[i] = null;
							river[0] = cur;
							return;
						}
						else // fish moving to bear slot
						{
							river[i] = null;
							return;
						}
					}
				}
				else // normal right slot movement
				{
					Animal rightSlot = river[i+1];
					
					if (rightSlot==null) // empty destination
					{
						river[i+1] = cur;
						river[i] = null;
						return;
					}
					else if (bothBears(rightSlot, cur)) // both bears
					{
						Bear curBear = (Bear) cur;
						Bear rightSlotBear = (Bear) rightSlot;
						
						if (sameGender(rightSlot, cur)) // same gender bears
						{
							if (curBear.getStrength() > rightSlotBear.getStrength()) // moving bear is stronger
							{
								river[i+1] = cur;
								river[i] = null;
								return;
							}
							else if (curBear.getStrength() < rightSlotBear.getStrength()) // moving bear is weaker
							{
								river[i] = null;
								return;
							}
							else // equal strengths
							{
								// nothing happens
								return;
							}
						}
						else // opposite gender (they make a baby)
						{
							addRandom(cur);
						}
					}
					else if (!cur.getAnimalType().equals(rightSlot.getAnimalType())) // bear and fish
					{
						if (cur.getAnimalType().equals("Fish")) // Fish moving to bear slot
						{
							river[i] = null;
							return;
						}
						else // bear moving to a fish slot
						{
							river[i] = null;
							river[i+1] = cur;
							return;
						}
					}
				}
			}
		}
	}

	/**
	 * Perform one cycle of the simulation, going through the cells of the
	 * river, updating ages, moving animals, creating animals, and killing
	 * animals, as explained in the Project Description.
	 */
	public void updateRiver() 
	{
		
		// performs a cycle
		for (int i = 0; i < this.length; ++i)
		{
			updateCell(i);
		}
		
		// checks ages and removes animal if age is maxed out
		Animal cur;
		
		for (int i = 0; i < this.length; ++i)
		{
			boolean incr;
			
			cur = river[i];
			if (cur!=null)
			{
				incr = cur.incrAge();
			}
			else
			{
				incr = true;
			}
			
			if (!incr)
			{
				river[i] = null;
			}
		}
	}

	/**
	 * Writes the river to an output file.
	 * 
	 * @param outputFileName
	 *            The name of the output file.
	 * @throws FileNotFoundException
	 */
	public void write(String outputFileName) throws FileNotFoundException 
	{
		try 
		{
			BufferedWriter br = new BufferedWriter(new FileWriter(outputFileName, true));
			
			String sRiv = this.toString();
			Scanner scan = new Scanner(sRiv);
			
			while(scan.hasNext())
			{
				br.write(scan.next());
				br.write("\t");
			}
			br.write("\n");
			br.close();
			scan.close();
		} 
		catch (IOException e) 
		{
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

	}

	/**
	 * Produces a string representation of the river following the format
	 * described in the Project Description.
	 * 
	 * @return The string representation of the river.
	 */
	public String toString() 
	{
		String str = "";
		for (Animal a : river)
		{
			if (a==null)
			{
				str += "--- ";
			}
			else if (a.getAnimalType().equals("Bear"))
			{
				str += "B";
				Bear bear = (Bear) a;
				if (bear.gender.equals(Gender.MALE))
				{
					str += "M";
				}
				else
				{
					str += "F";
				}
				
				int age = bear.getAge();
				str += (age + " ");
			}
			else if (a.getAnimalType().equals("Fish"))
			{
				str += "F";
				if (a.gender.equals(Gender.MALE))
				{
					str += "M";
				}
				else
				{
					str += "F";
				}
				int age = a.getAge();
				str += (age + " ");
			}
		}
		
		str = str.trim();
		return str;
	}
	
	/**
	 * Returns a Gender picked at random
	 * @return A random Gender
	 */
	private Gender getRandomGender()
	{
		int genderCase = RandomSingleton.getInstance().nextInt(2);
		Gender gen;
		if(genderCase==0)
		{
			gen = Gender.MALE;
		}
		else
		{
			gen = Gender.FEMALE;
		}
		
		return gen;
	}
	
	/**
	 * checks to see if both animals are bears
	 * @param otherSlot
	 * @param cur
	 * @return
	 */
	private static boolean bothBears(Animal otherSlot, Animal cur)
	{
		return (cur.getAnimalType().equals(otherSlot.getAnimalType()) && cur.getAnimalType().equals("Bear"));
	}
	
	private static boolean sameGender(Animal otherSlot, Animal cur)
	{
		return otherSlot.gender.equals(cur.gender);
	}
	
	private static boolean bothFish(Animal otherSlot, Animal cur)
	{
		return (cur.getAnimalType().equals(otherSlot.getAnimalType()) && cur.getAnimalType().equals("Fish"));
	}
	
}

